//============================================================================
// Name        : M3u8Downloader.cpp
// Author      : Minfang
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <fstream>
#include <boost/foreach.hpp>
#include <boost/tokenizer.hpp>
#include <cstdarg>
#include <curl/curl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <map>
using namespace std;
using namespace boost;

#define DOWNLOAD_REPEAT_TIME 5
#define DEFAULT_DOWNLOAD_TIME 3600
#define SLEEP_TIME 30

extern int errno;
typedef struct _curl_ts_data {

	ofstream tsfile;

} CURL_TS_DATA;

typedef struct _config {
	bool write_endlist_at_end;
	bool write_log;
	bool write_index;
} CONFIGURE;

ofstream log;

CONFIGURE config;

map<string, string> downloaded_ts_files;

static size_t curl_callback_write_file(void *contents, size_t size,
		size_t nmemb, void *userp) {
	size_t realsize = size * nmemb;

	char * p = (char*) contents;

	CURL_TS_DATA * cdata = (CURL_TS_DATA*) userp;

	if (realsize > 0) {
		cdata->tsfile.write(p, realsize);
	}

	return realsize;
}

inline std::string format(const char* fmt, ...) {
	int size = 512;
	char* buffer = 0;
	buffer = new char[size];
	va_list vl;
	va_start(vl, fmt);
	int nsize = vsnprintf(buffer, size, fmt, vl);
	if (size <= nsize) { //fail delete buffer and try again
		delete[] buffer;
		buffer = 0;
		buffer = new char[nsize + 1]; //+1 for /0
		nsize = vsnprintf(buffer, size, fmt, vl);
	}
	std::string ret(buffer);
	va_end(vl);
	delete[] buffer;
	return ret;
}

string getFileNameFromUrl(const char * url) {
	string cUrl = url;
	char_separator<char> sep("/");
	tokenizer<char_separator<char> > tokens(cUrl, sep);
	string fn;

	BOOST_FOREACH (const string& t, tokens) {
		fn = t;
	}

	return fn;
}

/**
 *
 */

int downloadFile(const char * url, const char * savedFilePath,
		const char * cookieFilePath) {

	log << "try to download:[" << url << "]" << endl;
	string filePath = savedFilePath;

	CURL_TS_DATA tdata;

	tdata.tsfile.open(filePath.c_str(), ofstream::binary);
	if (!tdata.tsfile.is_open()) {
		cerr << "can not write file:" << filePath << endl;
		return -1;
	}

	CURL *curl_handle;
	CURLcode res;
	/* init the curl session */
	curl_handle = curl_easy_init();

	curl_easy_setopt(curl_handle, CURLOPT_URL, url);

	if (cookieFilePath != NULL) {
		log << "set the cook option :" << cookieFilePath << endl;
		curl_easy_setopt(curl_handle, CURLOPT_COOKIEFILE, cookieFilePath);
		curl_easy_setopt(curl_handle, CURLOPT_COOKIEJAR, cookieFilePath);
	}
	curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION,
			curl_callback_write_file);
	curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void *)&tdata);
	curl_easy_setopt(curl_handle, CURLOPT_USERAGENT,
			"AppleCoreMedia/1.0.0.9A405 (iPad; U; CPU OS 5_0_1 like Mac OS X; en_us)");
	res = curl_easy_perform(curl_handle);

	curl_easy_cleanup(curl_handle);
	tdata.tsfile.close();
	/* check for errors */
	if (res != CURLE_OK) {
		return 0;
	} else {
		return 1;
	}

}
/**
 * to download a url several times until it successfully download
 */

int sureDownloadFile(const char * url, const char * savedFilePath,
		const char * cookieFilePath) {
	for (int i1 = 0; i1 < DOWNLOAD_REPEAT_TIME;) {
		int re = downloadFile(url, savedFilePath, cookieFilePath);
		if (re != 0) {
			return re;
		}
	}
	return 0;
}

bool is_file_existent(const char * fn) {
	struct stat sb;
	if (stat(fn, &sb) == -1) {
		//cerr << errno << endl;
		return false;
	} else {
		return true;
	}
}

string url2fileName(const char * url) {
	string re;
	for (int i1 = 0, total = strlen(url); i1 < total; i1++) {
		if (url[i1] == '/') {
			re += '_';
		} else if (url[i1] <= '9' && url[i1] >= '0') {
			re += url[i1];
		} else if (url[i1] <= 'z' && url[i1] >= 'a') {
			re += url[i1];
		} else if (url[i1] <= 'Z' && url[i1] >= 'A') {
			re += url[i1];
		} else if (url[i1] == '.') {
			re += url[i1];
		} else if (url[i1] == '-') {
			re += url[i1];
		}
	}

	return re;
}

int unit_test(const char * url, const char * saveFolder) {

	string fn = saveFolder;
	fn += "/";
	fn += getFileNameFromUrl(url);

	if (is_file_existent(fn.c_str())) {
		fn = saveFolder;
		fn += "/";
		fn += url2fileName(url);
	}
	log << "fn of " << url << " :" << fn << endl;
	int re = downloadFile(url, fn.c_str(), NULL);

	log << "download return:" << re << endl;

	return re;
}

void processM3u8file(const char * tempfile, const char * target,
		const char * m3u8Url, const char * saveFolder) {

	string m3u8FileName = getFileNameFromUrl(m3u8Url);
	char buff[10240];
	strcpy(buff, m3u8Url);
	char * p = strstr(buff, m3u8FileName.c_str());

	if (p != NULL) {
		*p = 0;
	}

	string m3u8FolderUrl = buff;

	ifstream fre(tempfile);

	ofstream ftarget(target);

	while (!fre.eof()) {
		fre.getline(buff, 10240);

		if (strstr(buff, ".ts") != NULL) {

			string ts_link;

			string tsFileName;

			if (strstr(buff, "http://") == NULL) {
				ts_link = format("%s%s", m3u8FolderUrl.c_str(), buff);
				tsFileName = buff;
			} else {
				ts_link = buff;
				tsFileName = url2fileName(buff);
			}

			log << "get ts link:[" << buff << "]" << endl;

			map<string, string>::iterator tt = downloaded_ts_files.find(
					ts_link);
			if (tt == downloaded_ts_files.end()) {
				//download it;

				string tsPath = format("%s/%s", saveFolder, tsFileName.c_str());
				string cookiePath = format("%s/cookie.txt", saveFolder);
				if (sureDownloadFile(ts_link.c_str(), tsPath.c_str(),
						cookiePath.c_str()) == 1) {
					log << "download ts:" << ts_link << " to " << tsPath
							<< endl;
					downloaded_ts_files[ts_link] = tsFileName;
					ftarget << tsFileName << endl;
				} else {
					log << "download fail:" << ts_link << endl;
					ftarget << ts_link << endl;
				}

			} else {
				log << "link:" << ts_link << " is existent as " << tt->second
						<< endl;
				ftarget << tt->second << endl;
			}

		} else {
			if (strlen(buff) != 0) {
				ftarget << buff << endl;
			}

			if (strstr(buff, "#EXT-X-ENDLIST")) {

			}
		}
	}

	if (config.write_endlist_at_end) {
		ftarget << "#EXT-X-ENDLIST" << endl;
	} else {
		ftarget << endl;
	}
	ftarget.close();
	fre.close();
	unlink(tempfile);
}

int download_m3u8(const char * url, const char * saveFolder) {

	string f1 = getFileNameFromUrl(url);
	time_t now = time(NULL);

	string tempfn = format("%s/%d.m3u8", saveFolder, now);
	string f2 = format("%d_%s", now, f1.c_str());
	string fn = format("%s/%s", saveFolder, f2.c_str());
	string cookiePath = format("%s/cookie.txt", saveFolder);
	int re = sureDownloadFile(url, tempfn.c_str(), cookiePath.c_str());

	if (re == 1) {
		processM3u8file(tempfn.c_str(), fn.c_str(), url, saveFolder);
	}

	log << "download m3u8 to:" << fn << " return:" << re << endl;

	if (re == 1 && config.write_index) {

		struct tm * timeinfo;

		timeinfo = localtime(&now);

		ofstream index_file;
		string indexFileName = format("%s/%s", saveFolder, "index.html");
		index_file.open(indexFileName.c_str(), ostream::app);

		if (!index_file.is_open()) {
			log << "can not open index file:" << index_file << endl;
		} else {
			string s = format(
					"<a href='%s'>%s %d-%02d-%02d %02d:%02d:%02d</a><p/>",
					f2.c_str(), f2.c_str(), timeinfo->tm_year + 1900,
					timeinfo->tm_mon + 1, timeinfo->tm_mday, timeinfo->tm_hour,
					timeinfo->tm_min, timeinfo->tm_sec);
			index_file << s << endl;
			index_file.close();
			log << "write " << s << " to index" << endl;
		}

	}

	return re;
}

void initConfig() {
	config.write_endlist_at_end = false;
	config.write_log = true;
	config.write_index = true;
}

int main(int argc, char ** argv) {
	initConfig();
	bool uTest = false;
	const char * url = NULL;
	const char * saveFolder = NULL;
	int download_time = DEFAULT_DOWNLOAD_TIME;

	int sleep_time = SLEEP_TIME;
	for (int i1 = 1; i1 < argc; i1++) {
		if (strcmp(argv[i1], "-U") == 0) {
			uTest = true;
		} else if (strcmp(argv[i1], "--with-endlist") == 0) {
			config.write_endlist_at_end = true;
		} else if (strcmp(argv[i1], "-h") == 0 || strcmp(argv[i1], "-help") == 0
				|| strcmp(argv[i1], "--help") == 0
				|| strcmp(argv[i1], "-?") == 0) {
			cout << argv[0] << " m3u8url save_folder "
					<< "[how_long_record_seconds:3600 is the default,-1 meas very long] "
					<< "[-h] [--with-endlist:add #EXT-X-ENDLIST in the end of each m3u8 file] "
					<< "[--interval-seconds how_many_seconds_to_download_m3u8_again:default is 30 seconds]"
					<< endl;
			return 0;
		} else if (strcmp(argv[i1], "--interval-seconds") == 0) {
			i1++;
			if (i1 < argc) {
				sleep_time = atoi(argv[i1]);

				if (sleep_time < 1) {
					sleep_time = SLEEP_TIME;
				}
			}
		}

		else if (url == NULL) {
			url = argv[i1];
		} else if (saveFolder == NULL) {
			saveFolder = argv[i1];
		} else {
			download_time = atoi(argv[i1]);
		}

		if (download_time < 0) {
			download_time = 10000000;
		}
	}

	if (url == NULL || saveFolder == NULL) {
		cerr << "url and savefolder are required " << endl;
		return 1;
	}

	curl_global_init(CURL_GLOBAL_ALL);
	string logName = format("%s/%s", saveFolder, "m.log");
	log.open(logName.c_str(), ostream::app);

	if (!log.is_open()) {
		cerr << "can not open log file:" << logName << endl;
	}

	log << "unit test:" << uTest << endl;
	log << "url:" << url << endl;
	log << "save folder:" << saveFolder << endl;
	log << "download duration:" << download_time << " seconds" << endl;
	log << "interval-seconds:" << sleep_time << " seconds" << endl;
	log << "with-endlist" << config.write_endlist_at_end << endl;

	if (uTest) {
		unit_test(url, saveFolder);
	} else {

		time_t end = time(NULL) + download_time + 2;

		for (; time(NULL) < end;) {
			download_m3u8(url, saveFolder);
			if (download_time == 0) {
				break;
			}
			sleep(sleep_time);
		}

	}

	curl_global_cleanup();
	log.close();
	return 0;
}
